import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * Created with IntelliJ IDEA.
 * User: Marcin
 * Date: 03.05.13
 * Time: 18:58
 * To change this template use File | Settings | File Templates.
 */
public class Map {

    public static final String DEFAULT_MAP = "/maps/house1.png";

    private BufferedImage img;
    private Dimension size;
    private boolean[][] points;
    private int agentPositionX;
    private int agentPositionY;
    public static int STEP_SIZE = 10;


    public Map(){
         this(DEFAULT_MAP);
    }

    public Map(String imgPath){

        URL imgURL = getClass().getResource(imgPath);
        if (imgURL == null) {
            System.err.println("Couldn't find file: " + imgPath);
        }
        //System.out.println(getClass().getResource(""));

        try {
            this.img = ImageIO.read(getClass().getResourceAsStream(imgPath));
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        this.setAgentPositionX(400);
        this.setAgentPositionY(400);
        this.size = new Dimension(img.getWidth(null), img.getHeight(null));
        points = new boolean[(int)size.getHeight()][(int)size.getWidth()];
        for(int y = 0; y < size.getHeight(); y++){
            for(int x = 0; x < size.getWidth(); x++){
                points[y][x] = img.getRGB(x, y) == Color.BLACK.getRGB();
            }

        }
    }

    public Image getImg() {
        return img;
    }

    public Dimension getSize() {
        return size;
    }

    public boolean[][] getPoints() {
        return points;
    }

    public double getDistanceFromNearestObject(int x, int y, double rotation){
        int x0 = x;
        int y0 = y;
        if(rotation == Math.PI*0.5){
            return y0 - 800;
        }
        if(rotation == Math.PI*1.5){
            return 800 - y0;
        }
        int x1;
        int y1;
        if(rotation < Math.PI*0.5 && rotation > Math.PI*1.5){
            x1 = 800;
        }
        else{
            x1 = 0;
        }

        if(x0 == 0 || x0 == 800 || y0 == 0 || y0 == 800){
            return calculateDistance(x, y, x0, y0);
        }
        double a = Math.tan(rotation);
        y1 = (int) ((800 - x0) * a + y0);
        int dx = x1 - x0;
        int dy = y1 - y0;
        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;
        int err = dx - dy;
        while(true){
            if(points[y0][x0] == true){
                return calculateDistance(x0, y0, x1, y1);
            }
            if(x0 == x1 && y0 == y1){
                return calculateDistance(x0, y0, x1, y1);
            }
            int e2 = 2*err;
            if(e2 > -dy){
                err -= dy;
                x0 += sx;
            }
            if(e2 < dx){
                err += dx;
                y0 += sy;
            }
        }
    }




    public Point getPointOfNearestObject(int x, int y, double rotation){
        //TODO mały triczek, bo w poprzednich obliczeniach miałem pełno błedów, można to później poprawić
        double maxLine= Math.sqrt(Math.pow(img.getHeight(), 2) + Math.pow(img.getWidth(), 2));
        int x2= getAgentPositionX() + (int)(Math.cos(rotation)*maxLine);
        int y2= getAgentPositionY() + (int)(Math.sin(rotation)*maxLine);

        int w = x2 - x ;
        int h = y2 - y ;
        int dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0 ;
        if (w<0) dx1 = -1 ; else if (w>0) dx1 = 1 ;
        if (h<0) dy1 = -1 ; else if (h>0) dy1 = 1 ;
        if (w<0) dx2 = -1 ; else if (w>0) dx2 = 1 ;
        int longest = Math.abs(w) ;
        int shortest = Math.abs(h) ;
        if (!(longest>shortest)) {
            longest = Math.abs(h) ;
            shortest = Math.abs(w) ;
            if (h<0) dy2 = -1 ; else if (h>0) dy2 = 1 ;
            dx2 = 0 ;
        }
        int numerator = longest >> 1 ;
        int tempX = x;
        int tempY = y;
        for (int i=0;i<=longest;i++) {
            //TODO teraz jest troche nie tak. Powinny byc warunki jeszcze gdy x<0 i y<0 łącznie bedzie ich 4
            //Jak zobaczy ściane to koniec // nie zadziala gdy planasza nie bedzie miała ścian!!!!
            if(x <= 0){
                x = 0;
            }
            if(y <= 0){
                y = 0;
            }
            if( x >= 800){
                x = 799;
            }
            if(y >= 800){
                y = 799;
            }
            if(isWall(x,y)){
                return new Point(tempX,tempY);
            }


            numerator += shortest ;
            if (!(numerator<longest)) {
                numerator -= longest ;
                x += dx1 ;
                y += dy1 ;
                tempX = x;
                tempY = y;
            } else {
                x += dx2 ;
                y += dy2 ;
                tempX = x;
                tempY = y;
            }
        }
        //System.out.println(tempX);
        //System.out.println(tempY);
        return new Point(x,y);
    }


    public double calculateDistance(int x0, int y0, int x1, int y1){
        return Math.sqrt(Math.pow(y1 - y0, 2) + Math.pow(x1 - x0, 2));
    }

    public boolean isWall(int x, int y){
           return points[y][x];
    }

    public boolean canMove(int x, int y, double rotation){
        //System.out.println(getDistanceFromNearestObject(x,y,rotation));
       // if (getDistanceFromNearestObject(x,y,rotation) <= stepSize+1){
       //   return false;
       // }
        //tempPoint =  getPointOfNearestObject(x,y,rotation);
        //System.out.println(tempPoint + " Distance: " + calculateDistance(tempPoint.x,tempPoint.y, x,y ));
        return true;
    }

    public int getAgentPositionX() {
        return agentPositionX;
    }

    public void setAgentPositionX(int agentPositionX) {
        this.agentPositionX = agentPositionX;
    }

    public int getAgentPositionY() {
        return agentPositionY;
    }

    public void setAgentPositionY(int agentPositionY) {
        this.agentPositionY = agentPositionY;
    }

    public Point moveAgent(Direction direction,double rotation){

        if(direction == Direction.FORWARD){

            int newPosX= getAgentPositionX() + (int)(Math.cos(rotation)*STEP_SIZE);
            int newPosY= getAgentPositionY() + (int)(Math.sin(rotation)*STEP_SIZE);
            Point point = getPointOfNearestObject(agentPositionX, agentPositionY, rotation);
            double newPosDistance = calculateDistance(agentPositionX, agentPositionY, newPosX, newPosY);
            double nearestWallDistance = calculateDistance(agentPositionX, agentPositionY, point.x, point.y);
            if(nearestWallDistance < newPosDistance + 10){
                newPosX = agentPositionX + (int)(Math.cos(rotation)*(Math.abs(agentPositionX - point.x) - 10));
                newPosY = agentPositionY + (int)(Math.sin(rotation)*(Math.abs(agentPositionY - point.y) - 10));
            }
            setAgentPositionX(newPosX);
            setAgentPositionY(newPosY);

        } else {
            int newPosX= getAgentPositionX() - (int)(Math.cos(rotation)*STEP_SIZE);
            int newPosY= getAgentPositionY() - (int)(Math.sin(rotation)*STEP_SIZE);
            Point point = getPointOfNearestObject(agentPositionX, agentPositionY, Math.PI + rotation);
            double newPosDistance = calculateDistance(agentPositionX, agentPositionY, newPosX, newPosY);
            double nearestWallDistance = calculateDistance(agentPositionX, agentPositionY, point.x, point.y);
            if(nearestWallDistance < newPosDistance + 10){
                newPosX = agentPositionX - (int)(Math.cos(rotation)*(Math.abs(agentPositionX - point.x) - 10));
                newPosY = agentPositionY - (int)(Math.sin(rotation)*(Math.abs(agentPositionY - point.y) - 10));
            }
            setAgentPositionX(newPosX);
            setAgentPositionY(newPosY);


        }
        return new Point(agentPositionX,agentPositionY);
    }
}
